home *** CD-ROM | disk | FTP | other *** search
/ Chip 2000 October / CHIP Turkiye Ekim 2000.iso / prog / naps / 04 / setup.exe / Gnucleus / ListCtrlEx.cpp < prev    next >
C/C++ Source or Header  |  2000-07-11  |  30KB  |  1,170 lines

  1. /********************************************************************************
  2.  
  3.     Gnucleus - A node application for the Gnutella network
  4.     Copyright (C) 2000 John Marshall
  5.  
  6.     This program is free software; you can redistribute it and/or modify
  7.     it under the terms of the GNU General Public License as published by
  8.     the Free Software Foundation; either version 2 of the License.
  9.  
  10.     This program is distributed in the hope that it will be useful,
  11.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.     GNU General Public License for more details.
  14.  
  15.     You should have received a copy of the GNU General Public License
  16.     along with this program; if not, write to the Free Software
  17.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  
  19.     For support, questions, comments, etc...
  20.     E-Mail: 
  21.         swabby@c0re.net
  22.     
  23.     Address:
  24.         21 Cadogan Way
  25.         Nashua, NH, USA 03062
  26.  
  27. ********************************************************************************/
  28.  
  29. #include "stdafx.h"
  30. #include <assert.h>
  31. #include "ListCtrlEx.h"
  32.  
  33. #ifdef _DEBUG
  34. #define new DEBUG_NEW
  35. #undef THIS_FILE
  36. static char THIS_FILE[] = __FILE__;
  37. #endif
  38.  
  39. // Used for inplace edits on the control
  40. #include "InPlaceEdit.h"
  41.  
  42. /////////////////////////////////////////////////////////////////////////////
  43. // CListCtrlEx
  44.  
  45. IMPLEMENT_DYNCREATE(CListCtrlEx, CListCtrl)
  46.  
  47. BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
  48.     //{{AFX_MSG_MAP(CListCtrlEx)
  49.     ON_WM_PAINT()
  50.     ON_WM_SETFOCUS()
  51.     ON_WM_KILLFOCUS()
  52.     ON_WM_MOUSEMOVE()
  53.     ON_WM_LBUTTONDOWN()
  54.     ON_WM_KEYDOWN()
  55.     ON_WM_DESTROY()
  56.     ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndlabeledit)
  57.     //}}AFX_MSG_MAP
  58.     ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHeaderClicked) 
  59.     ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHeaderClicked)
  60.     ON_MESSAGE(LVM_SETTEXTCOLOR, OnSetTextColor)
  61.     ON_MESSAGE(LVM_SETTEXTBKCOLOR, OnSetTextBkColor)
  62.     ON_MESSAGE(LVM_SETBKCOLOR, OnSetBkColor)
  63. END_MESSAGE_MAP()
  64.  
  65. /////////////////////////////////////////////////////////////////////////////
  66. // CListCtrlEx construction/destruction
  67.  
  68. CListCtrlEx::CListCtrlEx()
  69. {
  70.     // Don't Highlight entire row by default.
  71.     m_bFullRowSel = FALSE;
  72.     // Allow selections to be made all the way across the rows
  73.     m_bClientWidthSel = TRUE;
  74.  
  75.     // Default to not showing the grid lines.
  76.     m_bDrawGrid = FALSE;
  77.  
  78.     // Set default sorting behaviour
  79.     nSortedCol = -1; 
  80.     bSortAscending = TRUE; 
  81.   
  82.     // Allow the columns to be re-arranged
  83.     m_headerctrl.SetCallback( this, (void (CWnd::*)(int, int))DragColumn );
  84.  
  85.     m_clrText = ::GetSysColor(COLOR_WINDOWTEXT);
  86.     m_clrTextBk = ::GetSysColor(COLOR_WINDOW);
  87.     m_clrBkgnd = ::GetSysColor(COLOR_WINDOW);
  88.  
  89.     // Use generic sort routine by default, disable and enable 
  90.     // with EnableGenericSort().
  91.     m_bUseGenericSort = TRUE;
  92.  
  93.     
  94. }
  95.  
  96. CListCtrlEx::~CListCtrlEx()
  97. {
  98. }
  99.  
  100. /**
  101.   * Find the number of columns in the control
  102.   */
  103. int CListCtrlEx::GetNColumns()
  104. {
  105.    return( m_headerctrl.GetItemCount() );
  106. }
  107.  
  108. CString CListCtrlEx::GetColumnTitle( int n )
  109. {
  110.         // Get the column text and format
  111.         TCHAR buf[256];
  112.         HD_ITEM hditem;
  113.         
  114.         hditem.mask = HDI_TEXT | HDI_FORMAT;
  115.         hditem.pszText = buf;
  116.         hditem.cchTextMax = 255;
  117.  
  118.         m_headerctrl.GetItem( n , &hditem );
  119.     return( CString ( buf ) );
  120. }
  121.  
  122. /**
  123.   *   Return the column number associated with a title.
  124.   * This is necessary because the normal Windows method for adding
  125.   * text to a row depends upon knowing the column number for which
  126.   * you wish to insert text.  But we hijack the column numbers by
  127.   * allowing users to rearrange them...
  128.  */
  129. int CListCtrlEx::GetColumnNumber(  char *title )
  130. {
  131.   int nColumns = m_headerctrl.GetItemCount();
  132.  
  133.   for ( int i = 0; i < nColumns; i++ )
  134.   { 
  135.     HD_ITEM data;
  136.     char name[200];
  137.  
  138.     data.mask = HDI_TEXT;
  139.     data.pszText = (LPSTR)name;
  140.     data.cchTextMax = sizeof( name );
  141.  
  142.     m_headerctrl.GetItem( i, &data );
  143.  
  144.  
  145.     if ( strcmp( title, data.pszText) == 0 )
  146.     {
  147.       return( i );
  148.     }
  149.   }
  150.  
  151.   return( -1 );
  152. }
  153.  
  154. /**
  155.   * Used for the header column dragging behaviour
  156.   */
  157. void CListCtrlEx::DragColumn(int source, int dest)
  158. {
  159.     TCHAR sColText[160];
  160.  
  161.     // Insert a column at dest
  162.     LV_COLUMN       lv_col;
  163.     lv_col.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
  164.     lv_col.pszText = sColText;
  165.     lv_col.cchTextMax = 159;
  166.     GetColumn( source, &lv_col );
  167.     lv_col.iSubItem = dest;
  168.     InsertColumn( dest, &lv_col );
  169.  
  170.     // Adjust source col number since it might have changed 
  171.     // because a new column was inserted
  172.     if( source > dest ) 
  173.         source++;
  174.  
  175.     // Moving a col to position 0 is a special case
  176.     if( dest == 0 )
  177.         for( int i = GetItemCount()-1; i > -1 ; i-- )
  178.             SetItemText(i, 1, GetItemText( i, 0) );
  179.  
  180.     
  181.     // Copy sub item from source to dest
  182.     for( int i = GetItemCount()-1; i > -1 ; i-- )
  183.         SetItemText(i, dest, GetItemText( i, source ) );
  184.  
  185.     // Delete the source column, but not if it is the first
  186.     if( source != 0 )
  187.         DeleteColumn( source );
  188.     else
  189.     {
  190.         // If source col is 0, then copy col# 1 to col#0
  191.         // and then delete col# 1
  192.         GetColumn( 1, &lv_col );
  193.         lv_col.iSubItem = 0;
  194.         SetColumn( 0, &lv_col );
  195.         for( int i = GetItemCount()-1; i > -1 ; i-- )
  196.             SetItemText(i, 0, GetItemText( i, 1) );
  197.         DeleteColumn( 1 );
  198.     }
  199.  
  200.     Invalidate();
  201. }
  202.  
  203. /**
  204.   * Make sure the control is owner drawn, this means that we can 
  205.   * provide the custom drawing behaviour of; full row selection, and
  206.   * grid lines
  207.  */
  208. BOOL CListCtrlEx::PreCreateWindow(CREATESTRUCT& cs)
  209. {
  210.     // default is report view and full row selection
  211.     cs.style &= ~LVS_TYPEMASK;
  212.     cs.style |= LVS_REPORT | LVS_OWNERDRAWFIXED | LVS_EDITLABELS ;
  213.     m_bFullRowSel = TRUE;
  214.  
  215.     return(CListCtrl::PreCreateWindow(cs));
  216. }
  217.  
  218.  
  219. /**
  220.   * Hook into the message loop to show the tooltips.
  221.  */
  222. BOOL CListCtrlEx::PreTranslateMessage(MSG* pMsg) 
  223. {
  224.     m_tooltip.RelayEvent( pMsg );    
  225.     return CListCtrl::PreTranslateMessage(pMsg);
  226. }
  227.  
  228.  
  229. /**
  230.   * Allow the whole row to be highlighted in the list, not just the first
  231.   * column.
  232.  */
  233. BOOL CListCtrlEx::SetFullRowSel(BOOL bFullRowSel)
  234. {
  235.     // no painting during change
  236.     LockWindowUpdate();
  237.  
  238.     m_bFullRowSel = bFullRowSel;
  239.  
  240.     BOOL bRet;
  241.  
  242.     if (m_bFullRowSel)
  243.         bRet = ModifyStyle(0L, LVS_OWNERDRAWFIXED);
  244.     else
  245.         bRet = ModifyStyle(LVS_OWNERDRAWFIXED, 0L);
  246.  
  247.     // repaint window if we are not changing view type
  248.     if (bRet && (GetStyle() & LVS_TYPEMASK) == LVS_REPORT)
  249.         Invalidate();
  250.  
  251.     // repaint changes
  252.     UnlockWindowUpdate();
  253.  
  254.     return(bRet);
  255. }
  256.  
  257. /**
  258.   * Determine whether or not full row selection is enabled. 
  259.  */
  260. BOOL CListCtrlEx::GetFullRowSel()
  261. {
  262.     return(m_bFullRowSel);
  263. }
  264.  
  265. /////////////////////////////////////////////////////////////////////////////
  266. // CListCtrlEx drawing
  267.  
  268. /*
  269. * DrawItem() is called by the framework whenever an item needs to be drawn
  270. * for owner drawn controls.
  271. * Note:
  272. * <UL>
  273. *   <LI>LVS_SHOWSELALWAYS: non owner drawn controls show an item is
  274. *     highlighted when the control does not have focus with a different
  275. *     highlight color is (usually gray). This is not supported for
  276. *     this control.
  277. * </UL>
  278. */
  279.  
  280. void CListCtrlEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
  281. {
  282.     CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
  283.     int iSavedDC = pDC->SaveDC();             // Save DC state
  284.         
  285.     int iItem = lpDrawItemStruct->itemID;
  286.  
  287.     // Get item image and state info
  288.     LV_ITEM lvi;
  289.     lvi.mask = LVIF_IMAGE | LVIF_STATE;
  290.     lvi.iItem = iItem;
  291.     lvi.iSubItem = 0;
  292.     lvi.stateMask = 0xFFFF;        // get all state flags
  293.     GetItem(&lvi);
  294.  
  295.     bool bHighlight = (
  296.         (lvi.state & LVIS_DROPHILITED) ||
  297.         ((lvi.state & LVIS_SELECTED) && ((GetFocus() == this) || (GetStyle() & LVS_SHOWSELALWAYS)))
  298.         );
  299.  
  300.     // Get rectangles for drawing
  301.     CRect rcBounds;
  302.     CRect rcLabel;
  303.     CRect rcIcon;
  304.     GetItemRect(iItem, rcBounds, LVIR_BOUNDS);
  305.     GetItemRect(iItem, rcLabel, LVIR_LABEL);
  306.     GetItemRect(iItem, rcIcon, LVIR_ICON);
  307.     CRect rcItem(rcBounds);
  308.  
  309.     CString sLabel = GetItemText(iItem, 0);
  310.  
  311.     // Labels are offset by a certain amount  
  312.     // This offset is related to the width of a space character
  313.     int offset = pDC->GetTextExtent(_T(" "), 1 ).cx*2;
  314.  
  315.     rcBounds.left = rcLabel.left;
  316.     CRect rcWnd;
  317.     GetClientRect(&rcWnd);
  318.     if(m_bClientWidthSel && rcBounds.right<rcWnd.right)
  319.         rcBounds.right = rcWnd.right;
  320.  
  321.     // Draw the background
  322.     if(bHighlight)
  323.     {
  324.         pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
  325.         pDC->SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
  326.         pDC->FillRect(rcBounds, &CBrush(::GetSysColor(COLOR_HIGHLIGHT)));
  327.     }
  328.     else
  329.     {
  330.         pDC->FillRect(rcBounds, &CBrush(m_clrTextBk));
  331.     }
  332.  
  333.     // Set clip region
  334.     rcItem.right = rcItem.left + GetColumnWidth(0);
  335.  
  336.     // Draw state icon
  337.     if(lvi.state & LVIS_STATEIMAGEMASK)
  338.     {
  339.         int nImage = ((lvi.state & LVIS_STATEIMAGEMASK)>>12) - 1;
  340.         CImageList* pImageList = GetImageList(LVSIL_STATE);
  341.         if(pImageList)
  342.         {
  343.             pImageList->Draw(pDC, nImage,
  344.                 CPoint(rcItem.left, rcItem.top), ILD_TRANSPARENT);
  345.         }
  346.     }
  347.  
  348.     // Draw normal and overlay icon
  349.     CImageList* pImageList = GetImageList(LVSIL_SMALL);
  350.     if(pImageList)
  351.     {
  352.         UINT nOvlImageMask = lvi.state & LVIS_OVERLAYMASK;
  353.         pImageList->Draw(pDC, lvi.iImage, 
  354.             CPoint(rcIcon.left, rcIcon.top),
  355.             (bHighlight?ILD_BLEND50:0) | ILD_TRANSPARENT | nOvlImageMask );
  356.     }
  357.  
  358.     // Draw item label - Column 0
  359.     rcLabel.left += offset/2-1;
  360.     rcLabel.right -= offset;
  361.     pDC->DrawText(sLabel,-1,rcLabel,DT_LEFT | DT_SINGLELINE | DT_NOPREFIX
  362.         | DT_VCENTER | DT_END_ELLIPSIS);
  363.  
  364.     // Draw labels for remaining columns
  365.     LV_COLUMN lvc;
  366.     lvc.mask = LVCF_FMT | LVCF_WIDTH;
  367.  
  368.     for(int nColumn = 1; GetColumn(nColumn, &lvc); nColumn++)
  369.     {
  370.         rcItem.left = rcItem.right;
  371.         rcItem.right += lvc.cx;
  372.  
  373.         sLabel = GetItemText(iItem, nColumn);
  374.  
  375.         // Get the text justification
  376.         UINT nJustify = DT_LEFT;
  377.         switch(lvc.fmt & LVCFMT_JUSTIFYMASK)
  378.         {
  379.         case LVCFMT_RIGHT:
  380.             nJustify = DT_RIGHT;
  381.             break;
  382.         case LVCFMT_CENTER:
  383.             nJustify = DT_CENTER;
  384.             break;
  385.         default:
  386.             break;
  387.         }
  388.  
  389.         rcLabel = rcItem;
  390.         rcLabel.left += offset;
  391.         rcLabel.right -= offset;
  392.  
  393.         pDC->DrawText(sLabel, -1, rcLabel,
  394.             nJustify | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS);
  395.     }
  396.  
  397.     // draw focus rectangle if item has focus
  398.     if ((lvi.state & LVIS_FOCUSED) && (GetFocus() == this))
  399.         pDC->DrawFocusRect(rcBounds);
  400.  
  401.  
  402.     pDC->RestoreDC(iSavedDC);                 // Restore DC.
  403. }
  404.  
  405. /////////////////////////////////////////////////////////////////////////////
  406. // CListCtrlEx diagnostics
  407.  
  408. #ifdef _DEBUG
  409.  
  410. void CListCtrlEx::Dump(CDumpContext& dc) const
  411. {
  412.     CListCtrl::Dump(dc);
  413.  
  414.     dc << "m_bFullRowSel = " << m_bFullRowSel;
  415.     dc << "\n";
  416. }
  417.  
  418. #endif //_DEBUG
  419.  
  420.  
  421. /**
  422. * @param iRow    [in] row of cell
  423. * @param iColunm [in] column of cell
  424. * @return Rectangle corresponding to the given cell.
  425. */
  426.  
  427. CRect CListCtrlEx::GetCellRect(int iRow, int iColumn)const
  428. {
  429.     // Make sure that the ListView is in LVS_REPORT
  430.     if((GetStyle() & LVS_TYPEMASK) != LVS_REPORT)
  431.         return CRect(0,0,0,0);
  432.  
  433.     // Get the number of columns
  434.     {
  435.         CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  436.         int iColumnCount = pHeader->GetItemCount();
  437.         ASSERT(iColumn < iColumnCount);
  438.     }
  439.  
  440.     CRect rect;
  441.     GetItemRect(iRow, &rect, LVIR_BOUNDS);
  442.     // Now find the column
  443.     for(int colnum = 0; colnum < iColumn; colnum++)
  444.     {
  445.         rect.left += GetTrueColumnWidth(colnum);
  446.     }
  447.  
  448.     // Found the column
  449.     rect.right = rect.left + GetTrueColumnWidth(iColumn);
  450.  
  451.     RECT rectClient;
  452.     GetClientRect(&rectClient);
  453.     if(rect.right > rectClient.right)
  454.         rect.right = rectClient.right;
  455.  
  456.     return rect;
  457. }
  458.  
  459. /**
  460.  * @author Mark Findlay
  461.  */
  462. CString CListCtrlEx::GetTrueItemText(int row, int col)const
  463. {
  464.     // Get the header control 
  465.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  466.     _ASSERTE(pHeader);
  467.     
  468.     // get the current number of columns 
  469.     int nCount = pHeader->GetItemCount();
  470.     
  471.     // find the actual column requested. We will compare
  472.     // against hi.iOrder
  473.     for (int x=0; x< nCount; x++)
  474.     {
  475.         HD_ITEM hi = {0};
  476.         hi.mask = HDI_ORDER;
  477.         
  478.         BOOL bRet = pHeader->GetItem(x,&hi);
  479.         _ASSERTE(bRet);
  480.         if (hi.iOrder == col)
  481.         {
  482.             // Found it, get the associated text
  483.             return GetItemText(row,x);
  484.         }
  485.     }
  486.     
  487.     _ASSERTE(FALSE);
  488.     return "We better never fall through to here!";
  489. }
  490.  
  491. /**
  492.  * @author Mark Findlay
  493.  */
  494. int CListCtrlEx::GetTrueColumnWidth(int nCurrentPosition)const
  495. {
  496.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  497.     _ASSERTE(pHeader);
  498.     
  499.     int nCount = pHeader->GetItemCount();
  500.     
  501.     for (int x=0; x< nCount; x++)
  502.     {
  503.         HD_ITEM hi = {0};
  504.         hi.mask = HDI_WIDTH | HDI_ORDER;
  505.         
  506.         BOOL bRet = pHeader->GetItem(x,&hi);
  507.         _ASSERTE(bRet);
  508.         if (hi.iOrder == nCurrentPosition)
  509.             return hi.cxy;
  510.     }
  511.     
  512.     _ASSERTE(FALSE);
  513.     return 0; // We would never fall through to here!
  514. }
  515.  
  516. void CListCtrlEx::HideTitleTip()
  517. {
  518.     m_titletip.ShowWindow(SW_HIDE);
  519. }
  520.  
  521. /**
  522.   * @param point   [in]  point in client coordinates
  523.   * @param iRow    [out] row containing the point
  524.   * @param iColunm [out] column containing the point
  525.   *
  526.   * @author Matthew Bells
  527.   */
  528. bool CListCtrlEx::HitTestRowCol(CPoint& point, int& iRow, int& iColumn)
  529. {
  530.     // Make sure that the ListView is in LVS_REPORT
  531.     if((GetStyle() & LVS_TYPEMASK) != LVS_REPORT)
  532.         return false;
  533.  
  534.     int iPosX = point.x;
  535.     if(m_bFullRowSel)
  536.         iRow = HitTest(point);
  537.     // the above doesn't work when the control isn't owner drawn
  538.     // this will work on machines with Version 4.70 and later of Comctl32.dll.
  539.     else
  540.     {
  541.         LVHITTESTINFO hit_test_info;
  542.         hit_test_info.pt = point;
  543.         SubItemHitTest(&hit_test_info);
  544.         iRow = hit_test_info.iItem;
  545.     }
  546.  
  547.     // Get the number of columns
  548.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  549.     int iColumnCount = pHeader->GetItemCount();
  550.  
  551.     for(iColumn = 0; iColumn < iColumnCount; ++iColumn)
  552.     {
  553.         iPosX -= GetTrueColumnWidth(iColumn);
  554.         if(iPosX < 0)
  555.             break;
  556.     }
  557.     if(iColumn == iColumnCount)
  558.         iColumn = -1;
  559.  
  560.     return (iRow != -1 && iColumn != -1);
  561. }
  562.  
  563. // HitTestEx    - Determine the row index and column index for a point
  564. // Returns    - the row index or -1 if point is not over a row
  565. // point    - point to be tested.
  566. // col        - to hold the column index
  567. int CListCtrlEx::HitTestEx(CPoint &point, int *col) const
  568. {
  569.     int colnum = 0;
  570.     int row = HitTest( point, NULL );
  571.     
  572.     if( col ) *col = 0;
  573.  
  574.     // Make sure that the ListView is in LVS_REPORT
  575.     if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
  576.         return row;
  577.  
  578.     // Get the top and bottom row visible
  579.     row = GetTopIndex();
  580.     int bottom = row + GetCountPerPage();
  581.     if( bottom > GetItemCount() )
  582.         bottom = GetItemCount();
  583.     
  584.     // Get the number of columns
  585.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  586.     int nColumnCount = pHeader->GetItemCount();
  587.  
  588.     // Loop through the visible rows
  589.     for( ;row <=bottom;row++)
  590.     {
  591.         // Get bounding rect of item and check whether point falls in it.
  592.         CRect rect;
  593.         GetItemRect( row, &rect, LVIR_BOUNDS );
  594.         if( rect.PtInRect(point) )
  595.         {
  596.             // Now find the column
  597.             for( colnum = 0; colnum < nColumnCount; colnum++ )
  598.             {
  599.                 int colwidth = GetColumnWidth(colnum);
  600.                 if( point.x >= rect.left 
  601.                     && point.x <= (rect.left + colwidth ) )
  602.                 {
  603.                     if( col ) *col = colnum;
  604.                     return row;
  605.                 }
  606.                 rect.left += colwidth;
  607.             }
  608.         }
  609.     }
  610.     return -1;
  611. }
  612.  
  613.  
  614. // EditSubLabel        - Start edit of a sub item label
  615. // Returns        - Temporary pointer to the new edit control
  616. // nItem        - The row index of the item to edit
  617. // nCol            - The column of the sub item.
  618. CEdit* CListCtrlEx::EditSubLabel( int nItem, int nCol )
  619. {
  620.     // The returned pointer should not be saved
  621.  
  622.     // Make sure that the item is visible
  623.     if( !EnsureVisible( nItem, TRUE ) ) return NULL;
  624.  
  625.     // Make sure that nCol is valid
  626.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  627.     int nColumnCount = pHeader->GetItemCount();
  628.     if( nCol >= nColumnCount || GetColumnWidth(nCol) < 5 )
  629.         return NULL;
  630.  
  631.     // Get the column offset
  632.     int offset = 0;
  633.     for( int i = 0; i < nCol; i++ )
  634.         offset += GetColumnWidth( i );
  635.  
  636.     CRect rect;
  637.     GetItemRect( nItem, &rect, LVIR_BOUNDS );
  638.  
  639.     // Now scroll if we need to expose the column
  640.     CRect rcClient;
  641.     GetClientRect( &rcClient );
  642.     if( offset + rect.left < 0 || offset + rect.left > rcClient.right )
  643.     {
  644.         CSize size;
  645.         size.cx = offset + rect.left;
  646.         size.cy = 0;
  647.         Scroll( size );
  648.         rect.left -= size.cx;
  649.     }
  650.  
  651.     // Get Column alignment
  652.     LV_COLUMN lvcol;
  653.     lvcol.mask = LVCF_FMT;
  654.     GetColumn( nCol, &lvcol );
  655.     DWORD dwStyle ;
  656.     if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
  657.         dwStyle = ES_LEFT;
  658.     else if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
  659.         dwStyle = ES_RIGHT;
  660.     else dwStyle = ES_CENTER;
  661.  
  662.     rect.left += offset+4;
  663.     rect.right = rect.left + GetColumnWidth( nCol ) - 3 ;
  664.     if( rect.right > rcClient.right) rect.right = rcClient.right;
  665.  
  666.     dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL;
  667.     CEdit *pEdit = new CInPlaceEdit(nItem, nCol, GetItemText( nItem, nCol ));
  668.  
  669. #define IDC_IPEDIT 2
  670.     pEdit->Create( dwStyle, rect, this, IDC_IPEDIT );
  671.  
  672.  
  673.     return pEdit;
  674. }
  675.  
  676.  
  677. /**
  678.  * Repaint the selected items.
  679.  */
  680. void CListCtrlEx::RepaintSelectedItems()
  681. {
  682.     CRect rcItem;
  683.     CRect rcLabel;
  684.  
  685.     // Invalidate focused item so it can repaint
  686.  
  687.     int iItem = GetNextItem(-1, LVNI_FOCUSED);
  688.  
  689.     if(iItem != -1)
  690.     {
  691.         GetItemRect(iItem, rcItem, LVIR_BOUNDS);
  692.         GetItemRect(iItem, rcLabel, LVIR_LABEL);
  693.         rcItem.left = rcLabel.left;
  694.  
  695.         InvalidateRect(rcItem, FALSE);
  696.     }
  697.  
  698.     // Invalidate selected items depending on LVS_SHOWSELALWAYS
  699.  
  700.     if(!(GetStyle() & LVS_SHOWSELALWAYS))
  701.     {
  702.         for(iItem = GetNextItem(-1, LVNI_SELECTED);
  703.             iItem != -1; iItem = GetNextItem(iItem, LVNI_SELECTED))
  704.         {
  705.             GetItemRect(iItem, rcItem, LVIR_BOUNDS);
  706.             GetItemRect(iItem, rcLabel, LVIR_LABEL);
  707.             rcItem.left = rcLabel.left;
  708.  
  709.             InvalidateRect(rcItem, FALSE);
  710.         }
  711.     }
  712.  
  713.     UpdateWindow();
  714. }
  715.  
  716. /////////////////////////////////////////////////////////////////////////////
  717. // CListCtrlEx message handlers
  718.  
  719. void CListCtrlEx::OnDestroy() 
  720. {
  721.     m_titletip.DestroyWindow();
  722.  
  723.     CListCtrl::OnDestroy();
  724. }
  725.  
  726. void CListCtrlEx::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
  727. {
  728.     CListCtrl::OnChar(nChar, nRepCnt, nFlags);
  729.     HideTitleTip();
  730.     SendSelChangedNotification();
  731. }
  732.  
  733. void CListCtrlEx::OnKillFocus(CWnd* pNewWnd) 
  734. {
  735.     CListCtrl::OnKillFocus(pNewWnd);
  736.  
  737.     // This should be hidden no matter if another control is getting focus
  738.     // or the edit box.
  739.     HideTitleTip();
  740.  
  741.     // this really still has focus if one of its chilren (ie. the edit box)
  742.     // has focus
  743.     if(pNewWnd != NULL && pNewWnd->GetParent() == this)
  744.         return;
  745.  
  746.     // repaint items that should change appearance
  747.     if(m_bFullRowSel && (GetStyle() & LVS_TYPEMASK) == LVS_REPORT)
  748.         RepaintSelectedItems();
  749. }
  750.  
  751. void CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
  752. {
  753.     int iTest = GetKeyState(VK_LMENU);
  754.     // Shortcut to editing.
  755.     if((GetKeyState(VK_LMENU) & 0x8000) || (GetKeyState(VK_RMENU) & 0x8000))
  756.     {
  757.         int iRow;
  758.         int iColumn;
  759.         if(HitTestRowCol(point, iRow, iColumn))
  760.         {
  761.             SetFocus();
  762.             PostMessage(LVM_EDITLABEL, (WPARAM)iRow, 0);
  763.         }
  764.     }
  765.     else
  766.     {
  767.         CListCtrl::OnLButtonDown(nFlags, point);
  768.         ShowTitleTip(point);                    // Make sure TitleTip changes if needed.
  769.         SendSelChangedNotification();
  770.     }
  771. }
  772.  
  773. void CListCtrlEx::OnMouseMove(UINT nFlags, CPoint point)
  774. {
  775.     if( nFlags == 0 )
  776.     {
  777.         ShowTitleTip(point);                    // Make sure TitleTip changes if needed.
  778.     }
  779.     
  780.     CListCtrl::OnMouseMove(nFlags, point);
  781. }
  782.  
  783. /*
  784. * When the regular list view control repaints an item, it repaints only the
  785. * area occupied by defined columns. If the last column does not extend to the
  786. * end of the client area, then the space to the right of the last column is
  787. * not repainted. If we are highlighting the full row then this area also needs
  788. * to be invalidated so that the code in DrawItem() can add or remove the
  789. * highlighting from this area.
  790. */
  791.  
  792. void CListCtrlEx::OnPaint() 
  793. {
  794.     // in full row select mode, we need to extend the clipping region
  795.     // so we can paint a selection all the way to the right
  796.     if (m_bClientWidthSel &&
  797.         (GetStyle() & LVS_TYPEMASK) == LVS_REPORT && GetFullRowSel())
  798.     {
  799.         CRect rcAllLabels;
  800.         GetItemRect(0, rcAllLabels, LVIR_BOUNDS);
  801.  
  802.         CRect rcClient;
  803.         GetClientRect(&rcClient);
  804.         if(rcAllLabels.right < rcClient.right)
  805.         {
  806.             // need to call BeginPaint (in CPaintDC c-tor)
  807.             // to get correct clipping rect
  808.             CPaintDC dc(this);
  809.  
  810.             CRect rcClip;
  811.             dc.GetClipBox(rcClip);
  812.  
  813.             rcClip.left = min(rcAllLabels.right-1, rcClip.left);
  814.             rcClip.right = rcClient.right;
  815.  
  816.             InvalidateRect(rcClip, FALSE);
  817.             // EndPaint will be called in CPaintDC d-tor
  818.         }
  819.     }
  820.  
  821.     CListCtrl::OnPaint();
  822.  
  823.     // Draw the lines only for LVS_REPORT mode
  824.     if ( ( (GetStyle() & LVS_TYPEMASK) == LVS_REPORT ) &&
  825.          ( m_bDrawGrid ) )
  826.     {
  827.         // Get the number of columns
  828.         CClientDC dc(this );
  829.         CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
  830.         int nColumnCount = pHeader->GetItemCount();
  831.  
  832.         // The bottom of the header corresponds to the top of the line 
  833.         RECT rect;
  834.         pHeader->GetClientRect( &rect );
  835.         int top = rect.bottom;
  836.  
  837.         // Now get the client rect so we know the line length and
  838.         // when to stop
  839.         GetClientRect( &rect );
  840.  
  841.         // The border of the column is offset by the horz scroll
  842.         int borderx = 0 - GetScrollPos( SB_HORZ );
  843.         for( int i = 0; i < nColumnCount; i++ )
  844.         {
  845.             // Get the next border
  846.             borderx += GetColumnWidth( i );
  847.  
  848.             // if next border is outside client area, break out
  849.             if( borderx >= rect.right ) break;
  850.  
  851.             // Draw the line.
  852.             dc.MoveTo( borderx-1, top);
  853.             dc.LineTo( borderx-1, rect.bottom );
  854.         }
  855.  
  856.         // Draw the horizontal grid lines
  857.  
  858.         // First get the height
  859.         if( !GetItemRect( 0, &rect, LVIR_BOUNDS ))
  860.             return;
  861.  
  862.         int height = rect.bottom - rect.top;
  863.  
  864.         GetClientRect( &rect );
  865.         int width = rect.right;
  866.  
  867.         for( i = 1; i <= GetCountPerPage(); i++ )
  868.         {
  869.             dc.MoveTo( 0, top + height*i);
  870.             dc.LineTo( width, top + height*i );
  871.         }
  872.     }
  873.     
  874.     // Do not call CListCtrl::OnPaint() for painting messages
  875.  
  876. }
  877.  
  878. /**
  879.  * See if we should draw the grid.
  880.  */
  881. BOOL CListCtrlEx::GetDrawGrid()
  882. {
  883.   return( m_bDrawGrid );
  884. }
  885.  
  886. /**
  887.  * Set whether we should draw the grid or not.
  888.  */
  889. void CListCtrlEx::SetDrawGrid(BOOL bDrawGrid )
  890. {
  891.   m_bDrawGrid = bDrawGrid;
  892.   // Force a redraw
  893.   Invalidate();
  894. }
  895.  
  896. LRESULT CListCtrlEx::OnSetBkColor(WPARAM wParam, LPARAM lParam)
  897. {
  898.     m_clrBkgnd = (COLORREF)lParam;
  899.     return(Default());
  900. }
  901.  
  902. /*
  903. * This is another step to mimic the default behaviour of the list view
  904. * control. When the control loses focus, the focus rectangle around the
  905. * selected (focus) item has to be removed. When the control gets back
  906. * focus, then the focus rectangle has to be redrawn. Both these handlers
  907. * call the RepaintSelectedItems() helper function. 
  908. */
  909.  
  910. void CListCtrlEx::OnSetFocus(CWnd* pOldWnd) 
  911. {
  912.     CListCtrl::OnSetFocus(pOldWnd);
  913.  
  914.     // check if we are getting focus from label edit box
  915. //    if(pOldWnd!=NULL && pOldWnd->GetParent()==this)
  916. //        return;
  917.  
  918.     // repaint items that should change appearance
  919.     if(m_bFullRowSel && (GetStyle() & LVS_TYPEMASK)==LVS_REPORT)
  920.         RepaintSelectedItems();
  921. }
  922.  
  923. LRESULT CListCtrlEx::OnSetTextBkColor(WPARAM wParam, LPARAM lParam)
  924. {
  925.     m_clrTextBk = (COLORREF)lParam;
  926.     return(Default());
  927. }
  928.  
  929. LRESULT CListCtrlEx::OnSetTextColor(WPARAM wParam, LPARAM lParam)
  930. {
  931.     m_clrText = (COLORREF)lParam;
  932.     return(Default());
  933. }
  934.  
  935. void CListCtrlEx::PreSubclassWindow() 
  936. {
  937.     CListCtrl::PreSubclassWindow();
  938.  
  939.     // Add initialization code for our header
  940.     m_headerctrl.SubclassWindow( ::GetDlgItem(m_hWnd,0) );
  941.  
  942.     // Adding a tooltip to the header control.
  943.     m_tooltip.Create( this );
  944.     m_tooltip.AddTool( GetDlgItem(0), "Click to sort on this column.  Drag to rearrange columns." );
  945.  
  946.     m_titletip.Create(this);
  947.     m_titletip.SetBackground(CBrush(GetBkColor()));
  948. }
  949.  
  950. void CListCtrlEx::SendSelChangedNotification()
  951. {
  952.     NMHDR nmh;
  953.     nmh.hwndFrom = *this;
  954.     nmh.idFrom = GetDlgCtrlID();
  955.     nmh.code = LVNU_SELCHANGED;
  956.     GetParent()->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmh);
  957. }
  958.  
  959. void CListCtrlEx::ShowTitleTip(CPoint point)
  960. {
  961.     int iRow;
  962.     int iCol;
  963.     CRect rcIcon;
  964.  
  965.     if(HitTestRowCol(point, iRow, iCol))
  966.     {
  967.         CRect cellrect = GetCellRect(iRow, iCol);
  968.         // offset is equal to TextExtent of 2 space characters.
  969.         // Make sure you have the right font selected into the
  970.         // device context before calling GetTextExtent.
  971.         // You can save this value as a member variable.
  972.         // offset = pDC->GetTextExtent(_T(" "), 1 ).cx*2;
  973.         int offset = 6;
  974.         /*if(iCol == 0)
  975.         {
  976.             CRect rcLabel;
  977.             GetItemRect(iRow, &rcLabel, LVIR_LABEL );
  978.             offset = rcLabel.left - cellrect.left + offset / 2 - 1;
  979.         }*/
  980.  
  981.         if(iCol == 0)                    // TBD: test this with IE4
  982.             cellrect.left -= 2;    // Does it also move the first column???
  983.  
  984.         cellrect.top++;
  985.  
  986.         if(GetItemState(iRow, LVIS_SELECTED)
  987.             && (m_bFullRowSel || iCol == 0) )
  988.         {
  989.             m_titletip.SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
  990.             m_titletip.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
  991.         }
  992.         else
  993.         {
  994.             m_titletip.SetBkColor(m_clrTextBk);
  995.             m_titletip.SetTextColor(m_clrText);
  996.         }
  997.         
  998.         // move over allowing for icon to show
  999.         if ( iCol == 0 )
  1000.          {
  1001.              GetItemRect(iRow, rcIcon, LVIR_ICON);
  1002.              cellrect.left  += rcIcon.Width();
  1003.              cellrect.right += rcIcon.Width();
  1004.           }
  1005.  
  1006.         // make sure our calculated result is where the mouse is.
  1007.         if( (point.x >= cellrect.left) && (point.x <= cellrect.right)
  1008.                 && (point.y <= cellrect.bottom) && (point.y > cellrect.top) )
  1009.         m_titletip.Show(cellrect, GetTrueItemText(iRow, iCol), offset-1);
  1010.     }
  1011. }
  1012.  
  1013. void CListCtrlEx::OnHeaderClicked(NMHDR* pNMHDR, LRESULT* pResult) 
  1014. {
  1015.   HD_NOTIFY *phdn = (HD_NOTIFY *) pNMHDR;
  1016.  
  1017.   if( m_bUseGenericSort && phdn->iButton == 0 )
  1018.   {                // User clicked on header using left mouse button
  1019.     if( phdn->iItem == nSortedCol )
  1020.       bSortAscending = !bSortAscending;                
  1021.     else
  1022.       bSortAscending = TRUE;
  1023.  
  1024.     nSortedCol = phdn->iItem;
  1025.     SortTextItems( nSortedCol, bSortAscending );        
  1026.   }
  1027.     
  1028.   *pResult = 0;
  1029. }
  1030.  
  1031.  
  1032. // SortTextItems    - Sort the list based on column text
  1033. // Returns        - Returns true for success
  1034. // nCol            - column that contains the text to be sorted
  1035. // bAscending        - indicate sort order
  1036. // low            - row to start scanning from - default row is 0
  1037. // high            - row to end scan. -1 indicates last row
  1038. BOOL CListCtrlEx::SortTextItems( int nCol, BOOL bAscending, 
  1039.                     int low, int high)
  1040. {
  1041.     if( nCol >= ((CHeaderCtrl*)GetDlgItem(0))->GetItemCount() )
  1042.         return FALSE;
  1043.  
  1044.     ((CMyHeaderCtrl*)GetDlgItem( 0 ) )->RemoveAllSortImages();
  1045.     ((CMyHeaderCtrl*)GetDlgItem( 0 ) )->SetSortImage( nCol, bAscending );
  1046.     //    SetItemSortState( iSubItem , (SORT_STATE)!ssEachItem );
  1047.  
  1048.     if( high == -1 ) high = GetItemCount() - 1;
  1049.  
  1050.     int lo = low;
  1051.     int hi = high;
  1052.     CString midItem;
  1053.  
  1054.     if( hi <= lo ) return FALSE;
  1055.  
  1056.     midItem = GetItemText( (lo+hi)/2, nCol );
  1057.  
  1058.     // loop through the list until indices cross
  1059.     while( lo <= hi )
  1060.     {
  1061.         // rowText will hold all column text for one row
  1062.         CStringArray rowText;
  1063.  
  1064.         // find the first element that is greater than or equal to 
  1065.         // the partition element starting from the left Index.
  1066.         if( bAscending )
  1067.             while( ( lo < high ) && ( GetItemText(lo, nCol) < midItem ) )
  1068.                 ++lo;
  1069.         else
  1070.             while( ( lo < high ) && ( GetItemText(lo, nCol) > midItem ) )
  1071.                 ++lo;
  1072.  
  1073.         // find an element that is smaller than or equal to 
  1074.         // the partition element starting from the right Index.
  1075.         if( bAscending )
  1076.             while( ( hi > low ) && ( GetItemText(hi, nCol) > midItem ) )
  1077.                 --hi;
  1078.         else
  1079.             while( ( hi > low ) && ( GetItemText(hi, nCol) < midItem ) )
  1080.                 --hi;
  1081.  
  1082.         // if the indexes have not crossed, swap
  1083.         // and if the items are not equal
  1084.         if( lo <= hi )
  1085.         {
  1086.             // swap only if the items are not equal
  1087.             if( GetItemText(lo, nCol) != GetItemText(hi, nCol))
  1088.             {
  1089.                 // swap the rows
  1090.                 LV_ITEM lvitemlo, lvitemhi;
  1091.                 int nColCount = 
  1092.                     ((CHeaderCtrl*)GetDlgItem(0))->GetItemCount();
  1093.                 rowText.SetSize( nColCount );
  1094.                 int i;
  1095.                 for( i=0; i<nColCount; i++)
  1096.                     rowText[i] = GetItemText(lo, i);
  1097.                 lvitemlo.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
  1098.                 lvitemlo.iItem = lo;
  1099.                 lvitemlo.iSubItem = 0;
  1100.                 lvitemlo.stateMask = LVIS_CUT | LVIS_DROPHILITED | 
  1101.                         LVIS_FOCUSED |  LVIS_SELECTED | 
  1102.                         LVIS_OVERLAYMASK | LVIS_STATEIMAGEMASK;
  1103.  
  1104.                 lvitemhi = lvitemlo;
  1105.                 lvitemhi.iItem = hi;
  1106.  
  1107.                 GetItem( &lvitemlo );
  1108.                 GetItem( &lvitemhi );
  1109.  
  1110.                 for( i=0; i<nColCount; i++)
  1111.                     SetItemText(lo, i, GetItemText(hi, i));
  1112.  
  1113.                 lvitemhi.iItem = lo;
  1114.                 SetItem( &lvitemhi );
  1115.  
  1116.                 for( i=0; i<nColCount; i++)
  1117.                     SetItemText(hi, i, rowText[i]);
  1118.  
  1119.                 lvitemlo.iItem = hi;
  1120.                 SetItem( &lvitemlo );
  1121.             }
  1122.  
  1123.             ++lo;
  1124.             --hi;
  1125.         }
  1126.     }
  1127.  
  1128.     // If the right index has not reached the left side of array
  1129.     // must now sort the left partition.
  1130.     if( low < hi )
  1131.         SortTextItems( nCol, bAscending , low, hi);
  1132.  
  1133.     // If the left index has not reached the right side of array
  1134.     // must now sort the right partition.
  1135.     if( lo < high )
  1136.         SortTextItems( nCol, bAscending , lo, high );
  1137.  
  1138.     return TRUE;
  1139. }
  1140.   
  1141.  
  1142. /**
  1143.  * User has finished editting a label.
  1144.  */
  1145. void CListCtrlEx::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult) 
  1146. {
  1147.     LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
  1148.     LV_ITEM    *plvItem = &pDispInfo->item;
  1149.  
  1150.     if (plvItem->pszText != NULL)
  1151.     {
  1152.         SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText);
  1153.     }
  1154.     *pResult = 0;
  1155. }
  1156.  
  1157. BOOL CListCtrlEx::SetItemText( int nItem, int nSubItem, LPCTSTR lpszText )
  1158. {
  1159.     if ( nSubItem < 0 )
  1160.         return FALSE;
  1161.     else
  1162.         return( CListCtrl::SetItemText( nItem, nSubItem, lpszText ) );
  1163. }
  1164.  
  1165.  
  1166. void CListCtrlEx::EnableGenericSort(BOOL _use_generic)
  1167. {
  1168.     m_bUseGenericSort = _use_generic;
  1169. }
  1170.